package com.mingda.weathermagiccenter.utils;

import com.mingda.weathermagiccenter.utils.TrainDataVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.geotools.data.DataUtilities;
import org.geotools.data.crs.ForceCoordinateSystemFeatureResults;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.feature.DefaultFeatureCollection;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.SchemaException;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.filter.FilterFactoryImpl;
import org.geotools.filter.text.ecql.ECQL;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.FeatureLayer;
import org.geotools.map.Layer;
import org.geotools.map.MapContent;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.styling.Stroke;
import org.geotools.styling.*;
import org.locationtech.jts.geom.*;
import org.opengis.feature.Feature;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import wcontour.Contour;
import wcontour.Interpolate;
import wcontour.global.Border;
import wcontour.global.PointD;
import wcontour.global.PolyLine;
import wcontour.global.Polygon;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class LsosurfaceUtil {
    private static GeometryFactory geometryFactory = new GeometryFactory();

    public static String getColor(Map<Double, String> levelProps, Double minData) {
        List<Map.Entry<Double, String>> list = new ArrayList<>(levelProps.entrySet());
        Double min = null;
        int size = levelProps.entrySet().size() / 2;
        for (int i = 0; i < size; i++) {
            int startIndex = i * 2;
            int endIndex = startIndex + 1;
            Map.Entry<Double, String> minEntry = list.get(startIndex);
            Map.Entry<Double, String> maxEntry = list.get(endIndex);
            if (minData >= minEntry.getKey() && minData <= maxEntry.getKey()) {
                min = minEntry.getKey();
                break;
            }
        }
        if (Objects.isNull(min)) {
            return "";
        } else {
            return levelProps.get(min);
        }
    }

    public static double getDistanceByPoint(double lat1, double lng1, double lat2, double lng2) {
        double PI = 3.1415926;
        double EarthRadius = 6378137;
        double Rad = PI / 180.0;
        double radlat1 = lat1 * Rad;
        double radlat2 = lat2 * Rad;
        double a = radlat1 - radlat2;
        double b = (lng1 - lng2) * Rad;
        double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.pow(Math.sin(b / 2), 2)));
        s = s * EarthRadius;
        s = Math.round(s * 100) / 100;
        return s;
    }

    public static void generatePictureByName(List<TrainDataVO> trainDatas, String type, String externalResourcesRootPath, String area) {
        List<Double[]> trainDataList = trainDatas.stream().map(trainData -> new Double[]{trainData.getJd(), trainData.getWd(), trainData.getValue()}).collect(Collectors.toList());
        double[][] trainData = new double[trainDataList.size()][];
        double minData = trainDatas.stream().mapToDouble(TrainDataVO::getValue).min().getAsDouble();
        for (int i = 0; i < trainDataList.size(); i++) {
            Double[] doubles = trainDataList.get(i);
            double[] doubleArray = new double[doubles.length];
            for (int j = 0; j < doubles.length; j++) {
                doubleArray[j] = doubles[j];
            }
            trainData[i] = doubleArray;
        }
        String boundryFile = externalResourcesRootPath + "/"+area+".shp";
        String picture = externalResourcesRootPath + "/"+area;
        double[] dataInterval = null;
        // 注意配置训练数据各数值范围对应的颜色的时候必须确保训练数据都在配置范围内，否则会导致训练生成的等值面图颜色缺失。
        Map<Double, String> levelProps = new LinkedHashMap<>();
        // 数据类型 1：温度、2：降水、3：风、4:能见度
        switch (type) {
            case "1":
                levelProps.put(-1000.0, "#00048d");
                levelProps.put(-40.001, "#00048d");

                levelProps.put(-40.0, "#0000c0");
                levelProps.put(-35.001, "#0000c0");

                levelProps.put(-35.0, "#1212f0");
                levelProps.put(-30.001, "#1212f0");

                levelProps.put(-30.0, "#3b43ee");
                levelProps.put(-20.001, "#3b43ee");

                levelProps.put(-20.0, "#6d6aed");
                levelProps.put(-15.001, "#6d6aed");

                levelProps.put(-15.0, "#9e9ef8");
                levelProps.put(-10.001, "#9e9ef8");

                levelProps.put(-10.0, "#cbccf8");
                levelProps.put(-5.001, "#cbccf8");

                levelProps.put(-5.0, "#e8f1ff");
                levelProps.put(0.001, "#e8f1ff");

                levelProps.put(0.0, "#7fffe8");
                levelProps.put(4.9999, "#7fffe8");

                levelProps.put(5.0, "#00f5e4");
                levelProps.put(9.9999, "#00f5e4");

                levelProps.put(10.0, "#0fe2af");
                levelProps.put(11.9999, "#0fe2af");

                levelProps.put(12.0, "#0cc93b");
                levelProps.put(13.9999, "#0cc93b");

                levelProps.put(14.0, "#07e305");
                levelProps.put(15.9999, "#07e305");

                levelProps.put(16.0, "#4af749");
                levelProps.put(17.9999, "#4af749");

                levelProps.put(18.0, "#c0ff00");
                levelProps.put(19.9999, "#c0ff00");

                levelProps.put(20.0, "#fffb00");
                levelProps.put(21.9999, "#fffb00");

                levelProps.put(22.0, "#f4cb00");
                levelProps.put(24.9999, "#f4cb00");

                levelProps.put(25.0, "#ff9700");
                levelProps.put(27.9999, "#ff9700");

                levelProps.put(28.0, "#ff6406");
                levelProps.put(29.9999, "#ff6406");

                levelProps.put(30.0, "#f73a00");
                levelProps.put(34.9999, "#f73a00");

                levelProps.put(35.0, "#c80201");
                levelProps.put(39.9999, "#c80201");

                levelProps.put(40.0, "#610400");
                levelProps.put(999.9999, "#610400");

                dataInterval = getDataInterval(levelProps);
                picture = picture + "temp.png";
                break;
            case "2":

                levelProps.put(0.0, "#fefefe");
                levelProps.put(0.09, "#fefefe");

                levelProps.put(0.1, "#99ffa2");
                levelProps.put(9.99, "#99ffa2");

                levelProps.put(10.0, "#39c604");
                levelProps.put(24.99, "#39c604");

                levelProps.put(25.0, "#0086ff");
                levelProps.put(49.99, "#0086ff");

                levelProps.put(50.0, "#004dc2");
                levelProps.put(99.99, "#004dc2");

                levelProps.put(100.0, "#b800d1");
                levelProps.put(249.99, "#b800d1");

                levelProps.put(250.0, "#4f0233");
                levelProps.put(99999.99, "#4f0233");

                dataInterval = getDataInterval(levelProps);
                picture = picture + "rain.png";
                break;
            case "3":
                levelProps.put(0.0, "#164c88");
                levelProps.put(0.9, "#164c88");

                levelProps.put(1.0, "#3264c9");
                levelProps.put(1.9, "#3264c9");

                levelProps.put(2.0, "#57a4a8");
                levelProps.put(2.9, "#57a4a8");

                levelProps.put(3.0, "#91dc66");
                levelProps.put(3.9, "#91dc66");

                levelProps.put(4.0, "#ddeb72");
                levelProps.put(4.9, "#ddeb72");

                levelProps.put(5.0, "#c57432");
                levelProps.put(5.9, "#c57432");

                levelProps.put(6.0, "#b85826");
                levelProps.put(6.9, "#b85826");

                levelProps.put(7.0, "#a72e1a");
                levelProps.put(7.9, "#a72e1a");

                levelProps.put(8.0, "#740c0c");
                levelProps.put(8.9, "#740c0c");

                levelProps.put(9.0, "#5e0e22");
                levelProps.put(9.9, "#5e0e22");

                levelProps.put(10.0, "#671d54");
                levelProps.put(10.9, "#671d54");

                levelProps.put(11.0, "#52085d");
                levelProps.put(11.9, "#52085d");

                levelProps.put(12.0, "#37013f");
                levelProps.put(999.9, "#37013f");

                dataInterval = getDataInterval(levelProps);
                picture = picture + "winds.png";
                break;
            case "4":
                levelProps.put(0.0, "#3f1900");
                levelProps.put(0.09, "#3f1900");

                levelProps.put(0.1, "#6d2b00");
                levelProps.put(0.29, "#6d2b00");

                levelProps.put(0.3, "#a000f0");
                levelProps.put(0.99, "#a000f0");

                levelProps.put(1.0, "#f45c0a");
                levelProps.put(1.99, "#f45c0a");

                levelProps.put(2.0, "#ffb14e");
                levelProps.put(2.99, "#ffb14e");

                levelProps.put(3.0, "#fdff00");
                levelProps.put(4.99, "#fdff00");

                levelProps.put(5.0, "#73ff27");
                levelProps.put(9.99, "#73ff27");

                levelProps.put(10.0, "#c8eaff");
                levelProps.put(14.99, "#c8eaff");

                levelProps.put(15.0, "#95e0fb");
                levelProps.put(24.99, "#95e0fb");

                levelProps.put(25.0, "#fffeff");
                levelProps.put(99999999.99, "#fffeff");

                dataInterval = getDataInterval(levelProps);
                picture = picture + "vis.png";
                break;
            default:
        }
        String color = getColor(levelProps, minData);
        if (trainData.length == 0
                || Objects.isNull(dataInterval)
                || levelProps.isEmpty()
                || StringUtils.isEmpty(boundryFile)
                || StringUtils.isEmpty(picture)
        ) {
            return;
        }
        int[] size = new int[]{1000, 1000};
        try {
            FeatureCollection featureCollection = equiSurface(trainData, dataInterval, size, boundryFile);
            System.out.println("获取到结果:" + featureCollection);
            if (Objects.nonNull(featureCollection)) {
                addShapeLayer(featureCollection, levelProps, 1);
                Map params = getMap(featureCollection);
                System.out.println("输出图片.....");
                getMapContent(params, picture, color);
                System.out.println("输出图片成功...");
            } else {
                log.error("训练数据有误:{}", trainDatas);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static Map getMap(FeatureCollection featureCollection) {
        double minX = featureCollection.getBounds().getMinX();
        double minY = featureCollection.getBounds().getMinY();
        double maxX = featureCollection.getBounds().getMaxX();
        double maxY = featureCollection.getBounds().getMaxY();
        double xDistance = getDistanceByPoint(maxY, minX, maxY, maxX);
        double yDistance = getDistanceByPoint(maxY, maxX, minY, maxX);
        // 宽度确定，高度动态计算
        int height = (int) ((yDistance / xDistance) * 2048);

        double[] bbox = {minX, minY, maxX, maxY};
        Map params = new HashMap();
        params.put("bbox", bbox);
        params.put("width", 2048);
        params.put("height", height);
        return params;
    }

    private static double[] getDataInterval(Map<Double, String> levelProps) {
        double[] dataInterval;
        Double[] doubles = levelProps.keySet().toArray(new Double[levelProps.keySet().size()]);

        dataInterval = new double[doubles.length];
        for (int i = 0; i < doubles.length; i++) {
            dataInterval[i] = doubles[i];
        }
        Arrays.sort(dataInterval);
        return dataInterval;
    }

    //生成等值线数据，

    /**
     * 生成等值面图片
     *
     * @param trainData    等值点数据，3*n 的二维数组,按顺序是"x"组，"y"组,"值"组
     * @param dataInterval 数据分层级别
     * @param size         等值面插值点数量,越多越精细也越慢
     * @param boundryFile  等值面裁切shp文件路径
     */
    public static FeatureCollection equiSurface(double[][] trainData,
                                                double[] dataInterval,
                                                int[] size,
                                                String boundryFile) throws IOException {
        double _undefData = -9999.0;
        List<PolyLine> cPolylineList;
        List<Polygon> cPolygonList;
        int width = size[0],
                height = size[1];
        double[] _X = new double[width];
        double[] _Y = new double[height];
//        ClassPathResource readFile = new ClassPathResource(boundryFile);
//        File file = readFile.getFile();
        File file = new File(boundryFile);
        ShapefileDataStore shpDataStore = null;

        shpDataStore = new ShapefileDataStore(file.toURI().toURL());
        //设置编码，根据shp文件设置，一般中文操作系统的arcgis导出的是GBK，也可能是utf-8或其它，自行测试
        Charset charset = Charset.forName("GBK");
        shpDataStore.setCharset(charset);
        String typeName = shpDataStore.getTypeNames()[0];
        SimpleFeatureSource featureSource = null;
        featureSource = shpDataStore.getFeatureSource(typeName);
        SimpleFeatureCollection fc = featureSource.getFeatures();
        //获取shp边界，生成格网点
        double minX = fc.getBounds().getMinX();
        double minY = fc.getBounds().getMinY();
        double maxX = fc.getBounds().getMaxX();
        double maxY = fc.getBounds().getMaxY();
        Interpolate.createGridXY_Num(minX, minY, maxX, maxY, _X, _Y);
        double[][] _gridData;

        int nc = dataInterval.length;
        // IDW插值格网点
        _gridData = Interpolate.interpolation_IDW_Neighbor(trainData,
                _X, _Y, 12, _undefData);

        int[][] S1 = new int[_gridData.length][_gridData[0].length];
        // 获取轮廓
        List<Border> _borders = Contour.tracingBorders(_gridData, _X, _Y,
                S1, _undefData);
        // 生成等值线
        cPolylineList = Contour.tracingContourLines(_gridData, _X, _Y, nc,
                dataInterval, _undefData, _borders, S1);
        // 平滑
        cPolylineList = Contour.smoothLines(cPolylineList);
        // 生成等值面
        cPolygonList = Contour.tracingPolygons(_gridData, cPolylineList,
                _borders, dataInterval);

        // 等值面结果转换和裁切
        FeatureCollection polygonCollection = getFeatureCollection(cPolygonList);
        // 裁切
//       FeatureCollection source = clipFeatureCollection(fc, (SimpleFeatureCollection) polygonCollection);
        return polygonCollection;
    }

    private static SimpleFeatureCollection getSimpleFeatureCollection(String boundryFile) throws IOException {
        File file = new File(boundryFile);
        ShapefileDataStore shpDataStore = null;

        shpDataStore = new ShapefileDataStore(file.toURI().toURL());
        //设置编码，根据shp文件设置，一般中文操作系统的arcgis导出的是GBK，也可能是utf-8或其它，自行测试
        Charset charset = Charset.forName("GBK");
        shpDataStore.setCharset(charset);
        String typeName = shpDataStore.getTypeNames()[0];
        SimpleFeatureSource featureSource = null;
        featureSource = shpDataStore.getFeatureSource(typeName);
        return featureSource.getFeatures();
    }

    //结果转换
    public static FeatureCollection getFeatureCollection(List<Polygon> cPolygonList) {

        if (cPolygonList == null || cPolygonList.size() == 0) {
            return null;
        }
        FeatureCollection cs = null;
        List<Map<String, Object>> values = new ArrayList<Map<String, Object>>();
        try {
            for (Polygon pPolygon : cPolygonList) {
                //外圈
                LinearRing mainRing;
                Coordinate[] coordinates = new Coordinate[pPolygon.OutLine.PointList.size()];
                for (int i = 0, len = pPolygon.OutLine.PointList.size(); i < len; i++) {
                    PointD ptd = pPolygon.OutLine.PointList.get(i);
                    coordinates[i] = new Coordinate(ptd.X, ptd.Y);
                }
                try {
                    mainRing = geometryFactory.createLinearRing(coordinates);
                } catch (Exception e) {
//                    e.printStackTrace();
                    Coordinate[] coordinateArray = new Coordinate[coordinates.length + 1];
                    for (int i = 0; i < coordinates.length; i++) {
                        coordinateArray[i] = coordinates[i];
                    }
                    coordinateArray[coordinateArray.length - 1] = coordinateArray[0];
                    mainRing = geometryFactory.createLinearRing(coordinateArray);
                }
                //孔洞
                LinearRing[] holeRing = new LinearRing[pPolygon.HoleLines.size()];
                for (int i = 0; i < pPolygon.HoleLines.size(); i++) {
                    PolyLine hole = pPolygon.HoleLines.get(i);
                    Coordinate[] coordinates_h = new Coordinate[hole.PointList.size()];
                    for (int j = 0, len = hole.PointList.size(); j < len; j++) {
                        PointD ptd = hole.PointList.get(j);
                        coordinates_h[j] = new Coordinate(ptd.X, ptd.Y);
                    }
                    try {
                        holeRing[i] = geometryFactory.createLinearRing(coordinates_h);
                    } catch (Exception e) {
//                        e.printStackTrace();
                        Coordinate[] coordinateArray = new Coordinate[coordinates_h.length + 1];
                        for (int x = 0; x < coordinates_h.length; x++) {
                            coordinateArray[x] = coordinates_h[x];
                        }
                        coordinateArray[coordinateArray.length - 1] = coordinateArray[0];
                        holeRing[i] = geometryFactory.createLinearRing(coordinateArray);
                    }
                }
                org.locationtech.jts.geom.Polygon geo = geometryFactory.createPolygon(mainRing, holeRing);
                Map<String, Object> map = new HashMap<String, Object>();
                map.put("the_geom", geo);
                map.put("value", pPolygon.LowValue);
                values.add(map);
            }

            cs = LsosurfaceUtil.creatFeatureCollection(
                    "polygons",
                    "the_geom:Polygon:srid=4326,value:double",
                    values);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cs;
    }

    //结果裁切
    public static FeatureCollection clipFeatureCollection(FeatureCollection fc,
                                                          SimpleFeatureCollection gs) {
        FeatureCollection cs = null;
        try {
            List<Map<String, Object>> values = new ArrayList<Map<String, Object>>();
            FeatureIterator contourFeatureIterator = gs.features();
            FeatureIterator dataFeatureIterator = fc.features();
            while (dataFeatureIterator.hasNext()) {
                Feature dataFeature = dataFeatureIterator.next();
                Object dataGeometry = dataFeature.getProperty(
                        "the_geom").getValue();
                //
                if (dataGeometry instanceof MultiPolygon) {
                    MultiPolygon p = (MultiPolygon) dataGeometry;
                    while (contourFeatureIterator.hasNext()) {
                        Feature contourFeature = contourFeatureIterator.next();
                        Geometry contourGeometry = (Geometry) contourFeature
                                .getProperty("the_geom").getValue();
                        double v = (Double) contourFeature.getProperty("value")
                                .getValue();
                        if (p.intersects(contourGeometry)) {
                            Geometry geo = p.intersection(contourGeometry);
                            Map<String, Object> map = new HashMap<String, Object>();
                            map.put("the_geom", geo);
                            map.put("value", v);
                            values.add(map);
                        }
                    }
                } else {
                    Geometry p = (Geometry) dataGeometry;
                    while (contourFeatureIterator.hasNext()) {
                        Feature contourFeature = contourFeatureIterator.next();
                        Geometry contourGeometry = (Geometry) contourFeature
                                .getProperty("the_geom").getValue();
                        double v = (Double) contourFeature.getProperty("value")
                                .getValue();
                        if (p.intersects(contourGeometry)) {
                            Geometry geo = p.intersection(contourGeometry);
                            Map<String, Object> map = new HashMap<String, Object>();
                            map.put("the_geom", geo);
                            map.put("value", v);
                            values.add(map);
                        }
                    }
                }


            }

            contourFeatureIterator.close();
            dataFeatureIterator.close();


            cs = LsosurfaceUtil.creatFeatureCollection(
                    "MultiPolygons",
                    "the_geom:MultiPolygon:srid=4326,value:double",
                    values);

        } catch (SchemaException e) {
            e.printStackTrace();
        }

        return cs;
    }

    //geotools创建FeatureCollection
    public static FeatureCollection creatFeatureCollection(String typeName, String typeSpec, List<Map<String, Object>> values) throws SchemaException {
        SimpleFeatureType type = DataUtilities.createType(typeName, typeSpec);
        SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(type);
        DefaultFeatureCollection collection = new DefaultFeatureCollection();
        for (Map feat : values) {
            featureBuilder.reset();
            featureBuilder.add(feat.get("the_geom"));
            featureBuilder.add(feat.get("value"));
            SimpleFeature feature = featureBuilder.buildFeature(null);
            collection.add(feature);
        }
        return collection;
    }

    private static MapContent map = new MapContent();

    /**
     * 添加featureCollection等值面图层
     *
     * @param featureCollection 等值面要素几何
     * @param levelProps        色阶,结构如：{0.1:"#a5f38d"}
     * @param opacity           透明度
     */
    public static void addShapeLayer(FeatureCollection featureCollection, Map<Double, String> levelProps, float opacity) {
        try {
            // 由坐标顺序引发坐标变换，这三行由于修正数据，不加的话会出现要素漏缺。
            SimpleFeatureType simpleFeatureType = (SimpleFeatureType) featureCollection.getSchema();
            String crs = CRS.lookupIdentifier(simpleFeatureType.getCoordinateReferenceSystem(), true);
            featureCollection = new ForceCoordinateSystemFeatureResults(featureCollection,
                    CRS.decode(crs, true));
            //创建样式
            StyleFactory sf = new StyleFactoryImpl();
            FilterFactory ff = new FilterFactoryImpl();
            FeatureTypeStyle fts = sf.createFeatureTypeStyle();

            for (Map.Entry entry : levelProps.entrySet()) {
                double key = (Double) entry.getKey();
                String value = (String) entry.getValue();

                Fill fill = sf.createFill(ff.literal(value), ff.literal(opacity));
                Stroke stroke = sf.createStroke(ff.literal("#ffffff"), ff.literal(0), ff.literal(0));
                Symbolizer symbolizer = sf.createPolygonSymbolizer(stroke, fill, "the_geom");
                Rule rule = sf.createRule();
                rule.setName("dzm_" + key);
                rule.symbolizers().add(symbolizer);
                Filter filter = ECQL.toFilter("value=" + key);
                rule.setFilter(filter);
                fts.rules().add(rule);
            }

            Style style = sf.createStyle();
            style.setName("style_dzm");
            style.featureTypeStyles().add(fts);

            Layer layer = new FeatureLayer(featureCollection, style);
            map.addLayer(layer);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据四至坐标、长、宽像素获取地图内容，并生成图片
     *
     * @param params
     * @param imgPath
     */
    public static void getMapContent(Map params, String imgPath, String color) {
        Graphics g = null;
        try {
            double[] bbox = (double[]) params.get("bbox");
            double x1 = bbox[0], y1 = bbox[1],
                    x2 = bbox[2], y2 = bbox[3];
            int width = Integer.parseInt(params.get("width").toString()),
                    height = Integer.parseInt(params.get("height").toString());
            // 设置输出范围
            CoordinateReferenceSystem crs = DefaultGeographicCRS.WGS84;
            ReferencedEnvelope mapArea = new ReferencedEnvelope(x1, x2, y1, y2, crs);
            // 初始化渲染器
            StreamingRenderer sr = new StreamingRenderer();
            sr.setMapContent(map);
            // 初始化输出图像
            BufferedImage bi = new BufferedImage(width, height,
                    BufferedImage.TYPE_INT_ARGB);
            g = bi.getGraphics();
            if (StringUtils.isNotBlank(color)) {
                g.drawImage(bi, 0, 0, Color.decode(color), null);
            }
            ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                    RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            Rectangle rect = new Rectangle(0, 0, width, height);
            float alpha = 0.75f; // 设置透明度为75%
            AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha);
            ((Graphics2D) g).setComposite(ac);
            // 绘制地图
            sr.paint((Graphics2D) g, rect, mapArea);
            //将BufferedImage变量写入文件中。
            System.out.println("输出文件路径:" + imgPath);
            File file = new File(imgPath);
            String os = System.getProperty("os.name");
            //拼接完整连接
            if (!os.toLowerCase().startsWith("win")) {
                Runtime.getRuntime().exec("chmod 777 -R " + file);
            }
            writeImagesBatch(bi, "png", imgPath);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (Objects.nonNull(map)) {
                map.dispose();
            }
            if (Objects.nonNull(g)) {
                g.dispose();
            }
        }
    }

    /**
     * AsynchronousFileChannel提供了一系列方法来执行异步文件操作，包括但不限于读取、写入、锁定文件等。这些操作都是非阻塞的，
     * 因此它们可以在不占用线程的情况下执行，这使得它们非常适合在需要高吞吐量和低延迟的环境中使用
     *
     * @param image
     * @param format
     * @param outputPath
     * @throws IOException
     */
    private static void writeImagesBatch(BufferedImage image, String format, String outputPath) throws IOException {
        // 使用try-with-resources自动关闭outputStream
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            ImageIO.write(image, format, outputStream);

            Path path = Paths.get(outputPath);
            AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
            ByteBuffer buffer = ByteBuffer.wrap(outputStream.toByteArray());
            channel.write(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                @Override
                public void completed(Integer result, ByteBuffer attachment) {
                    System.out.println("Wrote " + result + " bytes to " + outputPath);
                    try {
                        channel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void failed(Throwable exc, ByteBuffer attachment) {
                    System.out.println("Failed to write to " + outputPath);
                    try {
                        channel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
